/*
 * Copyright 2008 p.langer
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * 	http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.ac.tuwien.big.easyholdem.probsim;

import at.ac.tuwien.big.easyholdem.poker.Cards;
import at.ac.tuwien.big.easyholdem.poker.Deck;
import at.ac.tuwien.big.easyholdem.poker.Evaluate;
import at.ac.tuwien.big.easyholdem.poker.HandRank;
import at.ac.tuwien.big.easyholdem.poker.HandRank.Rank;

/**
 * <p>
 * A simulator to calculate the probabilities for winning, making a pair, two
 * pair and so on.
 * </p>
 * 
 * <p>
 * Created on 23.01.2008 at 14:13:29.
 * </p>
 * 
 * @author Philip Langer <langer[at]big[dot]tuwien[dot]ac[dot]at>
 */
public class ProbabilitySimulator {

	/**
	 * the player's cards.
	 */
	private Cards playersCards = null;
	/**
	 * the cards on the board (community cards).
	 */
	private Cards boardCards = null;

	private long simulationCount = 10000;

	/**
	 * Creates a new {@link ProbabilitySimulator} specifying the card situation.
	 * 
	 * @param playersCards
	 *            the player's cards.
	 * @param boardCards
	 *            the cards on the board (community cards). The maximum are 5
	 *            cards.
	 */
	public ProbabilitySimulator(Cards playersCards, Cards boardCards) {
		super();
		setPlayersCards(playersCards);
		setBoardCards(boardCards);
	}

	/**
	 * Creates a new {@link ProbabilitySimulator} without specifying the card
	 * situation.
	 */
	public ProbabilitySimulator() {
		super();
	}

	/**
	 * Returns the currently set player's hole cards.
	 * 
	 * @return the player's cards.
	 */
	public Cards getPlayersCards() {
		return playersCards;
	}

	/**
	 * Sets the player's hole cards.
	 * 
	 * @param playersCards
	 *            the player's cards to set
	 */
	public void setPlayersCards(Cards playersCards) {
		this.playersCards = playersCards;
	}

	/**
	 * Returns the board cards.
	 * 
	 * @return the boardCards
	 */
	public Cards getBoardCards() {
		return boardCards;
	}

	/**
	 * Sets the board cards. The maximum are 5 cards.
	 * 
	 * @param boardCards
	 *            the boardCards to set
	 */
	public void setBoardCards(Cards boardCards) {
		if (boardCards == null) {
			boardCards = new Cards(5);
		}
		if (boardCards.size() > 5) {
			throw new IllegalArgumentException(
			"The maximum size of the board cards is 5.");
		}
			
		this.boardCards = boardCards;
	}

	/**
	 * Returns the count of simulations that are made for calculation.
	 * 
	 * @return the simulation count
	 */
	public long getSimulationCount() {
		return simulationCount;
	}

	/**
	 * Sets the count of simulations that are made for calculation.
	 * 
	 * @param simulationCount
	 *            the simulation count to set
	 */
	public void setSimulationCount(long simulationCount) {
		this.simulationCount = simulationCount;
	}

	/**
	 * Simulates the set situation and returns the probabilities.
	 * 
	 * @return the calculated probabilities.
	 */
	public HoldemProbability simulate() {

		// initialize the counters
		long winningCount = 0;
		long splitPotCount = 0;
		long pairCount = 0;
		long twoPairCount = 0;
		long threeOfAKindCount = 0;
		long straightCount = 0;
		long flushCount = 0;
		long fullHouseCount = 0;
		long fourOfAKindCount = 0;
		long straightFlushCount = 0;

		// simulate simulationCount times and count how often the player wins
		// and how often a specific rank is made
		for (long i = 0; i < getSimulationCount(); i++) {

			// create deck with unknown cards
			Deck deck = new Deck();
			deck.getCards().removeAll(playersCards);
			if (boardCards != null) {
				deck.getCards().removeAll(boardCards);
			}

			// shuffle
			deck.shuffle();

			// deal dealers cards
			Cards dealersCards = new Cards(2);
			dealersCards.add(deck.deal());
			dealersCards.add(deck.deal());
			
			// finish dealing board
			int cardsLeft = 0;
			Cards simBoard = new Cards(5);
			if (boardCards == null) {
				cardsLeft = 5;
			} else {
				cardsLeft = 5 - boardCards.size();
				simBoard.addAll(boardCards);
			}
			
			for (int j = 0; j < cardsLeft; j++) {
				simBoard.add(deck.deal());
			}
			
			
			// update counters
			HandRank playersHandRank = Evaluate.holdem(playersCards, simBoard);
			HandRank dealersHandRank = Evaluate.holdem(dealersCards, simBoard);
			// winning counters
			if (playersHandRank.compareTo(dealersHandRank) > 0) winningCount++;
			if (playersHandRank.compareTo(dealersHandRank) == 0) splitPotCount++;
			// rank counters
			if (playersHandRank.getRank().equals(Rank.PAIR)) pairCount++;
			if (playersHandRank.getRank().equals(Rank.TWOPAIR)) twoPairCount++;
			if (playersHandRank.getRank().equals(Rank.FLUSH)) flushCount++;
			if (playersHandRank.getRank().equals(Rank.FOUROFAKIND)) fourOfAKindCount++;
			if (playersHandRank.getRank().equals(Rank.FULLHOUSE)) fullHouseCount++;
			if (playersHandRank.getRank().equals(Rank.STRAIGHT)) straightCount++;
			if (playersHandRank.getRank().equals(Rank.STRAIGHTFLUSH)) straightFlushCount++;
			if (playersHandRank.getRank().equals(Rank.THREEOFAKIND)) threeOfAKindCount++;
		}

		// set counters to simulation count if the hands are made already
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.PAIR)) {
			pairCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.TWOPAIR)) {
			twoPairCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.THREEOFAKIND)) {
			threeOfAKindCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.STRAIGHT)) {
			straightCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.FLUSH)) {
			flushCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.FULLHOUSE)) {
			fullHouseCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.FOUROFAKIND)) {
			fourOfAKindCount = getSimulationCount();
		}
		if (Evaluate.holdem(playersCards, boardCards).getRank().equals(
				Rank.STRAIGHTFLUSH)) {
			straightFlushCount = getSimulationCount();
		}

		return new HoldemProbability((double)winningCount / getSimulationCount(),
				(double)splitPotCount / getSimulationCount(), (double)pairCount
						/ getSimulationCount(), (double)twoPairCount
						/ getSimulationCount(), (double)threeOfAKindCount
						/ getSimulationCount(), (double)straightCount
						/ getSimulationCount(), (double)flushCount
						/ getSimulationCount(), (double)fullHouseCount
						/ getSimulationCount(), (double)fourOfAKindCount
						/ getSimulationCount(), (double)straightFlushCount
						/ getSimulationCount());

	}

}
